package com.powernode.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.powernode.constant.QueueConstant;
import com.powernode.domain.*;
import com.powernode.dto.OrderConfirmParam;
import com.powernode.feign.OrderCartFeign;
import com.powernode.feign.OrderSkuFeign;
import com.powernode.feign.OrderUserAddrFeign;
import com.powernode.mapper.OrderMapper;
import com.powernode.mapper.OrderSettlementMapper;
import com.powernode.model.*;
import com.powernode.service.OrderItemService;
import com.powernode.service.OrderService;
import com.powernode.vo.OrderVo;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 内容:
 * 作者: cy
 */
@Service
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {


    @Autowired
    private OrderUserAddrFeign orderUserAddrFeign;

    @Autowired
    private OrderSkuFeign orderSkuFeign;

    @Autowired
    private OrderCartFeign orderCartFeign;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private OrderSettlementMapper orderSettlementMapper;


    @Autowired
    private Snowflake snowflake;


    /**
     * 订单确认
     * 0.先查询用户的默认收货地址
     * 1.区分是从商品进来的 还是从购物车进来的
     * 2.如果是从商品进来  组装店铺商品信息 和 计算价格
     * 3.如果是从购物车过来   先查购物车表 拿到购物车集合 拿到skuIds 在查商品 再组装和计算价格
     *
     * @param openId
     * @param orderConfirmParam
     * @return
     */
    @Override
    public OrderVo orderConfirm(String openId, OrderConfirmParam orderConfirmParam) {
        OrderVo orderVo = new OrderVo();
        // 先查询用户的默认收货地址  远程调用 member模块
        UserAddr userAddr = orderUserAddrFeign.getUserDefaultAddr(openId);
        orderVo.setUserAddr(userAddr);
        List<Long> basketIds = orderConfirmParam.getBasketIds();
        if (CollectionUtils.isEmpty(basketIds)) {
            // 从商品进来
            prodToConfirm(orderConfirmParam.getOrderItem(), orderVo);
        } else {
            // 从购物车过来
            cartToConfirm(basketIds, orderVo);
        }
        return orderVo;
    }

    /**
     * 从商品进来 确认
     * 1.拿到skuId 远程调用获取商品信息
     * 2.算价格 组装对象
     *
     * @param orderItem
     * @param orderVo
     */
    private void prodToConfirm(OrderItem orderItem, OrderVo orderVo) {
        // 远程调用获取sku信息
        List<Sku> skuList = orderSkuFeign.getSkuListByIds(Arrays.asList(orderItem.getSkuId()));
        if (CollectionUtils.isEmpty(skuList)) {
            throw new RuntimeException("服务器维护中");
        }
        // 从商品进来 只有一个店铺 创建店铺集合
        List<ShopOrder> shopCartOrders = new ArrayList<>(1);
        // 创建店铺
        ShopOrder shopOrder = new ShopOrder();
        // 创建商品信息集合
        ArrayList<OrderItem> orderItems = new ArrayList<>();
        // 填充orderItem 的值
        Sku sku = skuList.get(0);
        BeanUtil.copyProperties(sku, orderItem, true);
        // 计算它的总价
        Integer prodCount = orderItem.getProdCount();
        BigDecimal oneTotalMoney = sku.getPrice().multiply(new BigDecimal(prodCount));
        orderItem.setProductTotalAmount(oneTotalMoney);
        // 将商品信息对象添加到商品集合中
        orderItems.add(orderItem);
        // 将店铺对象添加到店铺集合中
        shopCartOrders.add(shopOrder);
        shopOrder.setShopCartItemDiscounts(orderItems);
        // 填充orderVo对象
        orderVo.setShopCartOrders(shopCartOrders);
        orderVo.setTotalCount(prodCount);
        orderVo.setTotalMoney(oneTotalMoney);
        orderVo.setActualTotal(oneTotalMoney);
        // 计算运费
        if (oneTotalMoney.compareTo(new BigDecimal(99)) < 0) {
            orderVo.setActualTotal(oneTotalMoney.add(new BigDecimal(6)));
            orderVo.setTransfee(new BigDecimal(6));
        }
    }

    /**
     * 从购物车过来确认
     * 1.远程调用购物车 集合
     * 2.拿到skuIds
     * 3.远程调用商品
     * 4.循环购物车集合
     *
     * @param basketIds
     * @param orderVo
     */
    private void cartToConfirm(List<Long> basketIds, OrderVo orderVo) {
        // 远程调用购物车
        List<Basket> basketList = orderCartFeign.getBasketsByIds(basketIds);
        if (CollectionUtils.isEmpty(basketList)) {
            throw new RuntimeException("服务器维护中");
        }
        List<Long> skuIds = basketList.stream()
                .map(Basket::getSkuId)
                .collect(Collectors.toList());
        // 远程调用商品
        List<Sku> skuList = orderSkuFeign.getSkuListByIds(skuIds);
        if (CollectionUtils.isEmpty(skuList)) {
            throw new RuntimeException("服务器维护中");
        }
        // 填充orderVo  按照店铺来区分
        // 将购物车按照店铺来分组
        // shopId 1   三个商品
        // shopId 2   一个商品
        Map<Long, List<Basket>> shopMap = basketList.stream()
                .collect(Collectors.groupingBy(Basket::getShopId));
        // 创建一个店铺集合
        ArrayList<ShopOrder> shopOrders = new ArrayList<>();
        // 创建一个商品数量集合
        ArrayList<Integer> allCounts = new ArrayList<>();
        // 创建单品总价集合
        ArrayList<BigDecimal> allTotalMoneys = new ArrayList<>();
        // 循环店铺分组
        shopMap.forEach((shopId, baskets) -> {
            // 创建店铺对象
            ShopOrder shopOrder = new ShopOrder();
            // 创建商品信息集合
            ArrayList<OrderItem> orderItems = new ArrayList<>();
            // 将商品信息集合添加到店铺对象中
            shopOrder.setShopCartItemDiscounts(orderItems);
            // 将店铺对象添加到店铺集合中
            shopOrders.add(shopOrder);
            // 循环baskets
            baskets.forEach(basket -> {
                OrderItem orderItem = new OrderItem();
                BeanUtil.copyProperties(basket, orderItem, true);
                // 过滤出符合条件的sku
                Sku sku1 = skuList.stream().filter(sku -> sku.getSkuId().equals(basket.getSkuId()))
                        .collect(Collectors.toList())
                        .get(0);
                BeanUtil.copyProperties(sku1, orderItem, true);
                // 计算单品总价
                Integer basketCount = basket.getBasketCount();
                BigDecimal oneTotalMoney = sku1.getPrice().multiply(new BigDecimal(basketCount));
                orderItem.setProductTotalAmount(oneTotalMoney);
                orderItem.setProdCount(basketCount);
                allCounts.add(basketCount);
                allTotalMoneys.add(oneTotalMoney);
                orderItems.add(orderItem);
            });
        });
        // 计算价格等数据
        Integer totalCount = allCounts.stream().reduce(Integer::sum).get();
        BigDecimal totalMoney = allTotalMoneys.stream().reduce(BigDecimal::add).get();
        orderVo.setShopCartOrders(shopOrders);
        orderVo.setTotalMoney(totalMoney);
        orderVo.setActualTotal(totalMoney);
        orderVo.setTotalCount(totalCount);
        // 计算运费
        if (totalMoney.compareTo(new BigDecimal(99)) < 0) {
            orderVo.setActualTotal(totalMoney.add(new BigDecimal(6)));
            orderVo.setTransfee(new BigDecimal(6));
        }
    }


    /**
     * 下预订单
     * 1.生成订单号
     * 2.清空对应的购物车
     * 3.减mysql库存
     * 4.减es库存
     * 5.写订单表
     * 6.给用户发个微信通知
     * 7.写延迟队列
     * ------------------
     * 分布式事务问题:本质 就是事务管理器不在同一个jvm下面
     * 事务？
     *
     * @param openId
     * @param orderVo
     * @return
     */
    @Override
    @Transactional
    public String doOrder(String openId, OrderVo orderVo) {
        // 生成订单号
        String orderSn = createOrderSn();

        // 清空对应的购物车   只能按照skuId和用户id来清除
        clearCart(openId, orderVo);

        // 减mysql库存  skuId count prodId count
        StockChange stockChange = changeMysqlStock(orderVo);

        // 减es库存
        changeEsStock(stockChange.getProdEsChanges());

        // 写订单表
        OrderMsgModel msgModel = writeOrder(openId, orderSn, orderVo, stockChange.getSkuChanges());

        // 给用户发个微信通知
        sendWxMsg(msgModel, openId);

        // 写延迟队列 如果用户不支付 30min到时见 就回滚订单
        writeMsQueue(orderSn);

        return orderSn;
    }

    /**
     * 写延迟队列
     *
     * @param orderSn
     */
    private void writeMsQueue(String orderSn) {
        rabbitTemplate.convertAndSend(QueueConstant.ORDER_MS_QUEUE, orderSn);
    }

    /**
     * 发送订单消息
     *
     * @param msgModel
     * @param openId
     */
    private void sendWxMsg(OrderMsgModel msgModel, String openId) {
        WxMsgModel wxMsgModel = new WxMsgModel();
        wxMsgModel.setToUser("onCu656voXtbbSoWVJ86HX_uGZ40");
        wxMsgModel.setTopColor("#173177");
        wxMsgModel.setTemplateId("7HZKCYpI0Yo2zN9e2KRmjMRg_FddMI-7CvMQxMR0H-Y");
        wxMsgModel.setUrl("http://www.bilibili.com");
        HashMap<String, Map<String, String>> map = new HashMap<>(8);
        map.put("time", WxMsgModel.build(new Date().toLocaleString(), "#173179"));
        map.put("goods", WxMsgModel.build(msgModel.getOrderName(), "#173179"));
        map.put("money", WxMsgModel.build(msgModel.getMoney().toString(), "#173179"));
        map.put("expire", WxMsgModel.build("3", "#173179"));
        wxMsgModel.setData(map);
        // 发送
        rabbitTemplate.convertAndSend(QueueConstant.WX_SEND_QUEUE, JSON.toJSONString(wxMsgModel));

    }

    /**
     * 写订单表
     * order
     * order_item
     * order_settlement 结算表
     *
     * @param openId
     * @param orderSn
     * @param orderVo
     */
    private OrderMsgModel writeOrder(String openId, String orderSn, OrderVo orderVo, List<SkuChange> skuChanges) {
        // 我需要重新计算金额
        List<Long> skuIds = skuChanges.stream()
                .map(SkuChange::getSkuId)
                .collect(Collectors.toList());
        // 远程调用  拿到skuList
        List<Sku> skuList = orderSkuFeign.getSkuListByIds(skuIds);
        if (CollectionUtils.isEmpty(skuList)) {
            throw new RuntimeException("服务器维护中");
        }
        ArrayList<BigDecimal> totalMoneyList = new ArrayList<>();
        // 写order_item表
        ArrayList<Integer> allCount = new ArrayList<>();
        ArrayList<OrderItem> orderItems = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem -> {
                orderItem.setOrderNumber(orderSn);
                orderItem.setRecTime(new Date());
                orderItem.setUserId(openId);
                orderItem.setCommSts(0);
                orderItems.add(orderItem);
                sb.append(orderItem.getProdName());
                sb.append(",");
                // 收集数量
                Integer prodCount = orderItem.getProdCount();
                allCount.add(prodCount);
                // 计算金额
                Sku sku1 = skuList.stream().filter(sku -> sku.getSkuId().equals(orderItem.getSkuId()))
                        .collect(Collectors.toList())
                        .get(0);
                BigDecimal oneTotalMoney = sku1.getPrice().multiply(new BigDecimal(prodCount));
                totalMoneyList.add(oneTotalMoney);
            });
        });
        boolean flag = orderItemService.saveBatch(orderItems);
        if (flag) {
            // 小米11pro,空军nick1号,
            StringBuilder orderNameSb = sb.deleteCharAt(sb.length() - 1);
            String orderName = orderNameSb.toString();
            // 支付金额这一块 不能使用前端的值
            Order order = Order.builder()
                    .orderNumber(orderSn)
                    .userId(openId)
                    .createTime(new Date())
                    .isPayed(false)
                    .status(1)
                    .prodName(orderName)
                    .remarks(orderVo.getRemarks())
                    .updateTime(new Date())
                    .addrOrderId(orderVo.getUserAddr().getAddrId())
                    .productNums(allCount.stream().reduce(Integer::sum).get())
                    .build();
            // 计算总价
            BigDecimal totalMoney = totalMoneyList.stream().reduce(BigDecimal::add).get();
            order.setTotalMoney(totalMoney);
            order.setActualTotal(totalMoney);
            if (totalMoney.compareTo(new BigDecimal(99)) < 0) {
                // 设置运费
                order.setActualTotal(totalMoney.add(new BigDecimal(6)));
                order.setFreightAmount(new BigDecimal(6));
            }
            int i = orderMapper.insert(order);
            if (i > 0) {
                // 写结算表
                OrderSettlement orderSettlement = new OrderSettlement();
                orderSettlement.setUserId(openId);
                orderSettlement.setCreateTime(new Date());
                orderSettlement.setOrderNumber(orderSn);
                orderSettlement.setIsClearing(0);
                orderSettlement.setVersion(0);
                int a = orderSettlementMapper.insert(orderSettlement);
                if (a > 0) {
                    return new OrderMsgModel(order.getActualTotal(), orderName);
                }

            }
        }
        throw new IllegalArgumentException("写订单表失败");
    }

    /**
     * 减es库存
     *
     * @param prodEsChanges
     */
    private void changeEsStock(List<ProdEsChange> prodEsChanges) {
        rabbitTemplate.convertAndSend(QueueConstant.PROD_ES_CHANGE_QUEUE, JSON.toJSONString(prodEsChanges));
    }

    /**
     * 减少mysql的库存
     * skuId count prodId count
     *
     * @param orderVo
     */
    private StockChange changeMysqlStock(OrderVo orderVo) {
        // 将数据 收集起来
        List<ProdEsChange> prodEsChanges = new ArrayList<>();
        List<SkuChange> skuChanges = new ArrayList<>();

        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem -> {
                Long skuId = orderItem.getSkuId();
                Long prodId = orderItem.getProdId();
                Integer count = orderItem.getProdCount() * -1;
                SkuChange skuChange = new SkuChange(skuId, count);
                skuChanges.add(skuChange);
                // prod 1  多 sku
                //  1     111(-1)   112(-1)
                // 先看集合中有没有prodId相同的对象
                List<ProdEsChange> oldProdEsChange = prodEsChanges.stream()
                        .filter(prodEsChange -> prodEsChange.getProdId().equals(prodId))
                        .collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(oldProdEsChange)) {
                    ProdEsChange prodEsChange = oldProdEsChange.get(0);
                    prodEsChange.setCount(prodEsChange.getCount() + count);
                } else {
                    ProdEsChange prodEsChange = new ProdEsChange(prodId, count);
                    prodEsChanges.add(prodEsChange);
                }
            });
        });
        // 创建一个大对象 将两个集合包起来 一起传输
        StockChange stockChange = new StockChange(prodEsChanges, skuChanges);
        // 远程调用
        Boolean flag = orderSkuFeign.changeStock(stockChange);
        if (!flag) {
            throw new RuntimeException("服务器维护中");
        }
        return stockChange;
    }

    /**
     * 清空购物车
     * delete form  basket where user_id = xxxxx and skuId  in (1,2,3)
     *
     * @param openId
     * @param orderVo
     */
    private void clearCart(String openId, OrderVo orderVo) {
        ArrayList<Long> skuIds = new ArrayList<>();
        orderVo.getShopCartOrders().forEach(shopOrder -> {
            shopOrder.getShopCartItemDiscounts().forEach(orderItem -> {
                Long skuId = orderItem.getSkuId();
                skuIds.add(skuId);
            });
        });
        // 远程调用 清空购物车
        orderCartFeign.clearCart(skuIds, openId);

    }

    /**
     * 生成订单号
     * 雪花算法  64位
     * 0(符号位)  41(时间戳)   10(机器id和机房id)   12(随机数)
     * 一个订单号有什么要求
     * 1.要有时间
     * 2.要单调递增
     * 3.不要连续
     * 问题
     * 1.回钟问题 当前生成的时间 比 上一次的 时间 还要小  肯定是时间回拨了  忍耐2s 再生成
     * 2.溢出问题  1ms 就有 超过4096个id生成请求进来   自旋到下一毫秒
     * 3.id数量用尽了   时间位全部占满   肯定有其他算法
     * // UUID 会重复
     * 为什么会重复
     *
     * @return
     */
    private String createOrderSn() {
        return snowflake.nextIdStr();
    }


    /**
     * 回滚操作
     * 1.修改订单状态
     * 2.回滚库存
     * 加事务的作用保持 数据的一致性
     *
     * @param order
     */
    @Override
    @Transactional
    public void orderRollback(Order order) {
        order.setStatus(6);
        order.setCloseType(1);
        order.setUpdateTime(new Date());
        int i = orderMapper.updateById(order);
        if (i > 0) {
            // 回滚库存  skuId prodId  count
            // 查询order_item表
            List<OrderItem> orderItems = orderItemService.list(new LambdaQueryWrapper<OrderItem>()
                    .eq(OrderItem::getOrderNumber, order.getOrderNumber())
            );
            // 将数据 收集起来
            List<ProdEsChange> prodEsChanges = new ArrayList<>();
            List<SkuChange> skuChanges = new ArrayList<>();

            orderItems.forEach(orderItem -> {
                Long skuId = orderItem.getSkuId();
                Long prodId = orderItem.getProdId();
                Integer count = orderItem.getProdCount();
                SkuChange skuChange = new SkuChange(skuId, count);
                skuChanges.add(skuChange);
                // prod 1  多 sku
                //  1     111(-1)   112(-1)
                // 先看集合中有没有prodId相同的对象
                List<ProdEsChange> oldProdEsChange = prodEsChanges.stream()
                        .filter(prodEsChange -> prodEsChange.getProdId().equals(prodId))
                        .collect(Collectors.toList());
                if (!CollectionUtils.isEmpty(oldProdEsChange)) {
                    ProdEsChange prodEsChange = oldProdEsChange.get(0);
                    prodEsChange.setCount(prodEsChange.getCount() + count);
                } else {
                    ProdEsChange prodEsChange = new ProdEsChange(prodId, count);
                    prodEsChanges.add(prodEsChange);
                }
            });
            // 创建一个大对象 将两个集合包起来 一起传输
            StockChange stockChange = new StockChange(prodEsChanges, skuChanges);
            // 远程调用
            Boolean flag = orderSkuFeign.changeStock(stockChange);
            // 回滚es 的 库存
            changeEsStock(prodEsChanges);
            if (!flag) {
                throw new RuntimeException("回滚库存失败，人工介入");
            }
        }

    }
}
